package be.ac.ua.codebase;

import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;

import net.jini.config.ConfigurationException;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.CannotJoinException;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.CrashCountException;
import net.jini.core.transaction.server.TransactionManager;
import net.jini.core.transaction.server.TransactionParticipant;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lookup.entry.Name;

public class BankAccount implements TransactionParticipant, BankAccountInterface, ProxyAccessor, DiscoveryListener {
	private static final long serialVersionUID = 1L;
	private Vector<ServiceRegistrar> fRegistrars = new Vector<ServiceRegistrar>();
	private UUID fId;
	private double fAmount = 0;
	private String fFirstName;
	private String fFamilyName;
	private String fPostalCode;
	
	private HashMap<Long, List<Double >> fPending = new HashMap<Long, List<Double> >();
	private TransactionParticipant fProxy;
		
	public BankAccount(String firstName, String familyName, int postalCode) throws RemoteException, ConfigurationException {
		fId = UUID.randomUUID();
		fAmount = 0;
		fFirstName = firstName;
		fFamilyName = familyName;
		fPostalCode = new Integer(postalCode).toString();
		
		//Exporter exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0), new BasicILFactory());		
		//fProxy = (TransactionParticipant)exporter.export(this);
	}
	
	public BankAccount(String firstName, String familyName, String postalCode) throws RemoteException, ConfigurationException {
		fId = UUID.randomUUID();
		fAmount = 0;
		fFirstName = firstName;
		fFamilyName = familyName;
		fPostalCode = postalCode;
		
		//Exporter exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0), new BasicILFactory());		
		//fProxy = (TransactionParticipant)exporter.export(this);
	}
	
	public TransactionManager getTransactionManager() throws RemoteException {
		TransactionManager transactionManager = null;
		
		Entry[] attributes = new Entry[1];
		attributes[0] = new Name ("Transaction Manager");
		Class[] classes = new Class[] {TransactionManager.class};
		
		for (int i = 0; i < fRegistrars.size(); i++) {			
			TransactionManager tmp = (TransactionManager) fRegistrars.get(i).lookup(new ServiceTemplate(null, classes, null));
			if (tmp != null) {
				transactionManager = tmp;
			}
		}
		
		if (transactionManager == null)
			System.out.println("[ERROR] - TransactionManager not found!");
		
		return transactionManager;
	}
	
	public void discarded(DiscoveryEvent evt) {
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.remove(registrars[n]);
		}		
	}

	public void discovered(DiscoveryEvent evt) { 
		ServiceRegistrar[] registrars = evt.getRegistrars();
		for (int n = 0; n < registrars.length; n++) { 
			fRegistrars.add(registrars[n]);
		}
	}
	
	public void export() throws ExportException {
		if(this.fProxy == null) {
			Exporter exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0), new BasicILFactory());		
			fProxy = (TransactionParticipant)exporter.export(this);
		}
	}
	
	public UUID getId() throws RemoteException {
		return fId;
	}
	
	public double getAmount() throws RemoteException {
		return this.fAmount;
	}
	
	public void credit(double amount) throws RemoteException {
		this.fAmount += amount;
	}
	
	public void debit(double amount) throws RemoteException {
		this.fAmount -= amount;
	}
	
	public void credit(double amount, TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, CannotJoinException, CrashCountException, RemoteException {
		List<Double> list;
		if(fPending.containsKey(transactionId)) {
			list = fPending.get(transactionId);
		} else {
			list = new LinkedList<Double>();
		}
		list.add(amount);
		fPending.put(transactionId, list);
		this.export();
		transactionManager.join(transactionId, fProxy, 0);
	}
	
	public void debit(double amount, TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, CannotJoinException, CrashCountException, RemoteException {
		List<Double> list;
		if(fPending.containsKey(transactionId)) {
			list = fPending.get(transactionId);
		} else {
			list = new LinkedList<Double>();
		}
		list.add(-amount);
		fPending.put(transactionId, list);
		this.export();
		transactionManager.join(transactionId, fProxy, 0);
	}

	public void abort(TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, RemoteException {
		fPending.remove(transactionId);
	}

	public void commit(TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, RemoteException {
		double oldAmount = fAmount;
		List<Double> list = fPending.get(transactionId);
		Iterator it = list.iterator();
		while(it.hasNext()) {
			fAmount += (Double) it.next();
		}
		System.out.println("[BANK ACCOUNT] - Changed amount from " + oldAmount + " to " + fAmount);
		fPending.remove(transactionId);
	}

	public int prepare(TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, RemoteException {
        if (!fPending.containsKey(transactionId)) {
        	System.out.println("HIER");
        	return ABORTED;
        }
        double flow = 0;
        List<Double> list = fPending.get(transactionId);
		Iterator it = list.iterator();
		while(it.hasNext()) {
			flow += (Double) it.next();
		}
		
        if (fAmount + flow < 0) {
                return ABORTED;
        }
        
        return PREPARED;
	}

	public int prepareAndCommit(TransactionManager transactionManager, long transactionId) throws UnknownTransactionException, RemoteException {
		int result = prepare(transactionManager, transactionId);
		if	(result == PREPARED) {
			commit(transactionManager, transactionId);
			result = COMMITTED;
		}
		return result;
	}

	public Object getProxy() {
		return fProxy;
	}
}
